<template>
  <div
    :class="[{
      [classNameActive]: enabled,
      [classNameDragging]: dragging,
      [classNameResizing]: resizing,
      [classNameDraggable]: draggable,
      [classNameResizable]: resizable
    }, className]"
    :style="style"
    @mousedown="elementMouseDown"
    @touchstart="elementTouchDown"
  >
    <div
      :class="[classNameHandle, classNameHandle + '-' + handle]"
      :key="handle"
      :style="handleStyle(handle)"
      @mousedown.stop.prevent="handleDown(handle, $event)"
      @touchstart.stop.prevent="handleTouchDown(handle, $event)"
      v-for="handle in actualHandles"
    >
      <slot :name="handle"></slot>
    </div>
    <slot></slot>
  </div>
</template>

<script>
  import { matchesSelectorToParentElements, getComputedSize, addEvent, removeEvent } from '../utils/dom'
  import { computeWidth, computeHeight, restrictToBounds, snapToGrid } from '../utils/fns'

  const events = {
    mouse: {
      start: 'mousedown',
      move: 'mousemove',
      stop: 'mouseup'
    },
    touch: {
      start: 'touchstart',
      move: 'touchmove',
      stop: 'touchend'
    }
  }

  // 禁止用户选取
  const userSelectNone = {
    userSelect: 'none',
    MozUserSelect: 'none',
    WebkitUserSelect: 'none',
    MsUserSelect: 'none'
  }
  // 用户选中自动
  const userSelectAuto = {
    userSelect: 'auto',
    MozUserSelect: 'auto',
    WebkitUserSelect: 'auto',
    MsUserSelect: 'auto'
  }

  let eventsFor = events.mouse

  export default {
    replace: true,
    name: 'vue-draggable-resizable',
    props: {
      uid: {},
      className: {
        type: String,
        default: 'vdr' //  vdr
      },
      classNameDraggable: {
        type: String,
        default: 'draggable'
      },
      classNameResizable: {
        type: String,
        default: 'resizable'
      },
      classNameDragging: {
        type: String,
        default: 'dragging'
      },
      classNameResizing: {
        type: String,
        default: 'resizing'
      },
      classNameActive: {
        type: String,
        default: 'active'
      },
      classNameHandle: {
        type: String,
        default: 'handle'
      },
      disableUserSelect: {
        type: Boolean,
        default: true
      },
      enableNativeDrag: {
        type: Boolean,
        default: false
      },
      preventDeactivation: {
        type: Boolean,
        default: false
      },
      active: {
        type: Boolean,
        default: false
      },
      draggable: {
        type: Boolean,
        default: true
      },
      resizable: {
        type: Boolean,
        default: true
      },
      // 锁定宽高比
      lockAspectRatio: {
        type: Boolean,
        default: false
      },
      w: {
        type: [Number, String],
        default: 200,
        validator: (val) => {
          if (typeof val === 'number') {
            return val > 0
          }
          return val === 'auto'
        }
      },
      h: {
        type: [Number, String],
        default: 200,
        validator: (val) => {
          if (typeof val === 'number') {
            return val > 0
          }
          return val === 'auto'
        }
      },
      minWidth: {
        type: Number,
        default: 0,
        validator: (val) => val >= 0
      },
      minHeight: {
        type: Number,
        default: 0,
        validator: (val) => val >= 0
      },
      maxWidth: {
        type: Number,
        default: null,
        validator: (val) => val >= 0
      },
      maxHeight: {
        type: Number,
        default: null,
        validator: (val) => val >= 0
      },
      x: {
        type: Number,
        default: 0
      },
      y: {
        type: Number,
        default: 0
      },
      z: {
        type: [String, Number],
        default: 'auto',
        validator: (val) => (typeof val === 'string' ? val === 'auto' : val >= 0)
      },
      handles: {
        type: Array,
        default: () => ['tl', 'tm', 'tr', 'mr', 'br', 'bm', 'bl', 'ml'],
        validator: (val) => {
          const s = new Set(['tl', 'tm', 'tr', 'mr', 'br', 'bm', 'bl', 'ml'])

          return new Set(val.filter(h => s.has(h))).size === val.length
        }
      },
      dragHandle: {
        type: String,
        default: null
      },
      dragCancel: {
        type: String,
        default: null
      },
      axis: {
        type: String,
        default: 'both',
        validator: (val) => ['x', 'y', 'both'].includes(val)
      },
      grid: {
        type: Array,
        default: () => [1, 1]
      },
      parent: {
        type: [Boolean, String],
        default: false
      },
      onDragStart: {
        type: Function,
        default: () => true
      },
      onDrag: {
        type: Function,
        default: () => true
      },
      onResizeStart: {
        type: Function,
        default: () => true
      },
      onResize: {
        type: Function,
        default: () => true
      },
      // 冲突检测
      isConflictCheck: {
        type: Boolean, default: false
      },
      // 元素对齐
      snap: {
        type: Boolean, default: false
      },
      // 当调用对齐时，用来设置组件与组件之间的对齐距离，以像素为单位
      snapTolerance: {
        type: Number,
        default: 5,
        validator: function(val) {
          return typeof val === 'number'
        }
      },
      // 缩放比例 使用 scale 使用
      scaleRatio: {
        type: Number,
        default: 1,
        validator: (val) => typeof val === 'number'
      },
      // handle是否缩放
      handleInfo: {
        type: Object,
        default: () => {
          return {
            size: 8,
            offset: -5,
            switch: true
          }
        }
      }
    },

    data: function() {
      return {
        left: this.x,
        top: this.y,
        right: null,
        bottom: null,

        width: null,
        height: null,
        widthTouched: false,
        heightTouched: false,
        aspectFactor: null,

        parentWidth: null,
        parentHeight: null,

        minW: this.minWidth,
        minH: this.minHeight,

        maxW: this.maxWidth,
        maxH: this.maxHeight,

        handle: null,
        enabled: this.active,
        resizing: false,
        dragging: false,
        zIndex: this.z
      }
    },

    created: function() {
      // eslint-disable-next-line 无效的prop：minWidth不能大于maxWidth
      if (this.maxWidth && this.minWidth > this.maxWidth) console.warn('[Vdr warn]: Invalid prop: minWidth cannot be greater than maxWidth')
      // eslint-disable-next-line 无效prop：minHeight不能大于maxHeight'
      if (this.maxWidth && this.minHeight > this.maxHeight) console.warn('[Vdr warn]: Invalid prop: minHeight cannot be greater than maxHeight')

      this.resetBoundsAndMouseState()
    },
    mounted: function() {
      if (!this.enableNativeDrag) {
        this.$el.ondragstart = () => false
      }

      const [parentWidth, parentHeight] = this.getParentSize()

      this.parentWidth = parentWidth
      this.parentHeight = parentHeight
      const [width, height] = getComputedSize(this.$el)
      this.aspectFactor = (this.w !== 'auto' ? this.w : width) / (this.h !== 'auto' ? this.h : height)
      this.width = this.w !== 'auto' ? this.w : width
      this.height = this.h !== 'auto' ? this.h : height
      this.right = this.parentWidth - this.width - this.left
      this.bottom = this.parentHeight - this.height - this.top

      this.settingAttribute()

      addEvent(document.documentElement, 'mousedown', this.deselect)
      addEvent(document.documentElement, 'touchend touchcancel', this.deselect)

      addEvent(window, 'resize', this.checkParentSize)
    },
    beforeDestroy: function() {
      removeEvent(document.documentElement, 'mousedown', this.deselect)
      removeEvent(document.documentElement, 'touchstart', this.handleUp)
      removeEvent(document.documentElement, 'mousemove', this.move)
      removeEvent(document.documentElement, 'touchmove', this.move)
      removeEvent(document.documentElement, 'mouseup', this.handleUp)
      removeEvent(document.documentElement, 'touchend touchcancel', this.deselect)

      removeEvent(window, 'resize', this.checkParentSize)
    },

    methods: {
      // 重置边界和鼠标状态
      resetBoundsAndMouseState() {
        this.mouseClickPosition = { mouseX: 0, mouseY: 0, x: 0, y: 0, w: 0, h: 0 }

        this.bounds = {
          minLeft: null,
          maxLeft: null,
          minRight: null,
          maxRight: null,
          minTop: null,
          maxTop: null,
          minBottom: null,
          maxBottom: null
        }
      },
      // 检查父元素大小
      checkParentSize() {
        if (this.parent) {
          const [newParentWidth, newParentHeight] = this.getParentSize()

          this.parentWidth = newParentWidth
          this.parentHeight = newParentHeight
        }
      },
      // 获取父元素大小
      getParentSize() {
        if (this.parent === true) {
          const style = window.getComputedStyle(this.$el.parentNode, null)
          return [
            parseInt(style.getPropertyValue('width'), 10),
            parseInt(style.getPropertyValue('height'), 10)
          ]
        }
        if (typeof this.parent === 'string') {
          const parentNode = document.querySelector(this.parent)
          if (!(parentNode instanceof HTMLElement)) {
            throw new Error(`The selector ${this.parent} does not match any element`)
          }
          return [parentNode.offsetWidth, parentNode.offsetHeight]
        }

        return [null, null]
      },
      // 元素触摸按下
      elementTouchDown(e) {
        eventsFor = events.touch

        this.elementDown(e)
      },
      elementMouseDown(e) {
        eventsFor = events.mouse
        this.elementDown(e)
      },

      // 元素按下
      elementDown(e) {
        if (e instanceof MouseEvent && e.which !== 1) {
          return
        }

        const target = e.target || e.srcElement

        if (this.$el.contains(target)) {
          if (this.onDragStart(e) === false) {
            return
          }

          if (
            (this.dragHandle && !matchesSelectorToParentElements(target, this.dragHandle, this.$el)) ||
            (this.dragCancel && matchesSelectorToParentElements(target, this.dragCancel, this.$el))
          ) {
            this.dragging = false

            return
          }

          if (!this.enabled) {
            this.enabled = true

            this.$emit('activated')
            this.$emit('update:active', true)
          }

          if (this.draggable) {
            this.dragging = true
          }

          this.mouseClickPosition.mouseX = e.touches ? e.touches[0].pageX : e.pageX
          this.mouseClickPosition.mouseY = e.touches ? e.touches[0].pageY : e.pageY

          this.mouseClickPosition.left = this.left
          this.mouseClickPosition.right = this.right
          this.mouseClickPosition.top = this.top
          this.mouseClickPosition.bottom = this.bottom
          this.mouseClickPosition.w = this.width
          this.mouseClickPosition.h = this.height

          if (this.parent) {
            this.bounds = this.calcDragLimits()
          }

          addEvent(document.documentElement, eventsFor.move, this.move)
          addEvent(document.documentElement, eventsFor.stop, this.handleUp)
        }
      },
      // 计算移动范围
      calcDragLimits() {
        return {
          minLeft: this.left % this.grid[0],
          maxLeft: Math.floor((this.parentWidth - this.width - this.left) / this.grid[0]) * this.grid[0] + this.left,
          minRight: this.right % this.grid[0],
          maxRight: Math.floor((this.parentWidth - this.width - this.right) / this.grid[0]) * this.grid[0] + this.right,
          minTop: this.top % this.grid[1],
          maxTop: Math.floor((this.parentHeight - this.height - this.top) / this.grid[1]) * this.grid[1] + this.top,
          minBottom: this.bottom % this.grid[1],
          maxBottom: Math.floor((this.parentHeight - this.height - this.bottom) / this.grid[1]) * this.grid[1] + this.bottom
        }
      },
      // 取消
      deselect(e) {
        const target = e.target || e.srcElement
        const regex = new RegExp(this.className + '-([trmbl]{2})', '')

        if (!this.$el.contains(target) && !regex.test(target.className)) {
          if (this.enabled && !this.preventDeactivation) {
            this.enabled = false

            this.$emit('deactivated')
            this.$emit('update:active', false)
          }

          removeEvent(document.documentElement, eventsFor.move, this.handleResize)
        }

        this.resetBoundsAndMouseState()
      },
      // 控制柄触摸按下
      handleTouchDown(handle, e) {
        eventsFor = events.touch

        this.handleDown(handle, e)
      },
      // 控制柄按下
      handleDown(handle, e) {
        if (e instanceof MouseEvent && e.which !== 1) {
          return
        }

        if (this.onResizeStart(handle, e) === false) {
          return
        }

        if (e.stopPropagation) e.stopPropagation()

        // Here we avoid a dangerous recursion by faking
        // corner handles as middle handles
        if (this.lockAspectRatio && !handle.includes('m')) {
          this.handle = 'm' + handle.substring(1)
        } else {
          this.handle = handle
        }

        this.resizing = true

        this.mouseClickPosition.mouseX = e.touches ? e.touches[0].pageX : e.pageX
        this.mouseClickPosition.mouseY = e.touches ? e.touches[0].pageY : e.pageY
        this.mouseClickPosition.left = this.left
        this.mouseClickPosition.right = this.right
        this.mouseClickPosition.top = this.top
        this.mouseClickPosition.bottom = this.bottom
        this.mouseClickPosition.w = this.width
        this.mouseClickPosition.h = this.height

        this.bounds = this.calcResizeLimits()

        addEvent(document.documentElement, eventsFor.move, this.handleResize)
        addEvent(document.documentElement, eventsFor.stop, this.handleUp)
      },

      // 计算调整大小范围
      calcResizeLimits() {
        let minW = this.minW
        let minH = this.minH
        let maxW = this.maxW
        let maxH = this.maxH

        const aspectFactor = this.aspectFactor
        const [gridX, gridY] = this.grid
        const width = this.width
        const height = this.height
        const left = this.left
        const top = this.top
        const right = this.right
        const bottom = this.bottom

        if (this.lockAspectRatio) {
          if (minW / minH > aspectFactor) {
            minH = minW / aspectFactor
          } else {
            minW = aspectFactor * minH
          }

          if (maxW && maxH) {
            maxW = Math.min(maxW, aspectFactor * maxH)
            maxH = Math.min(maxH, maxW / aspectFactor)
          } else if (maxW) {
            maxH = maxW / aspectFactor
          } else if (maxH) {
            maxW = aspectFactor * maxH
          }
        }

        maxW = maxW - (maxW % gridX)
        maxH = maxH - (maxH % gridY)

        const limits = {
          minLeft: null,
          maxLeft: null,
          minTop: null,
          maxTop: null,
          minRight: null,
          maxRight: null,
          minBottom: null,
          maxBottom: null
        }

        if (this.parent) {
          limits.minLeft = left % gridX
          limits.maxLeft = left + Math.floor((width - minW) / gridX) * gridX
          limits.minTop = top % gridY
          limits.maxTop = top + Math.floor((height - minH) / gridY) * gridY
          limits.minRight = right % gridX
          limits.maxRight = right + Math.floor((width - minW) / gridX) * gridX
          limits.minBottom = bottom % gridY
          limits.maxBottom = bottom + Math.floor((height - minH) / gridY) * gridY

          if (maxW) {
            limits.minLeft = Math.max(limits.minLeft, this.parentWidth - right - maxW)
            limits.minRight = Math.max(limits.minRight, this.parentWidth - left - maxW)
          }

          if (maxH) {
            limits.minTop = Math.max(limits.minTop, this.parentHeight - bottom - maxH)
            limits.minBottom = Math.max(limits.minBottom, this.parentHeight - top - maxH)
          }

          if (this.lockAspectRatio) {
            limits.minLeft = Math.max(limits.minLeft, left - top * aspectFactor)
            limits.minTop = Math.max(limits.minTop, top - left / aspectFactor)
            limits.minRight = Math.max(limits.minRight, right - bottom * aspectFactor)
            limits.minBottom = Math.max(limits.minBottom, bottom - right / aspectFactor)
          }
        } else {
          limits.minLeft = null
          limits.maxLeft = left + Math.floor((width - minW) / gridX) * gridX
          limits.minTop = null
          limits.maxTop = top + Math.floor((height - minH) / gridY) * gridY
          limits.minRight = null
          limits.maxRight = right + Math.floor((width - minW) / gridX) * gridX
          limits.minBottom = null
          limits.maxBottom = bottom + Math.floor((height - minH) / gridY) * gridY

          if (maxW) {
            limits.minLeft = -(right + maxW)
            limits.minRight = -(left + maxW)
          }

          if (maxH) {
            limits.minTop = -(bottom + maxH)
            limits.minBottom = -(top + maxH)
          }

          if (this.lockAspectRatio && (maxW && maxH)) {
            limits.minLeft = Math.min(limits.minLeft, -(right + maxW))
            limits.minTop = Math.min(limits.minTop, -(maxH + bottom))
            limits.minRight = Math.min(limits.minRight, -left - maxW)
            limits.minBottom = Math.min(limits.minBottom, -top - maxH)
          }
        }

        return limits
      },
      // 移动
      move(e) {
        if (this.resizing) {
          this.handleResize(e)
        } else if (this.dragging) {
          this.handleDrag(e)
        }
      },
      // 元素移动
      async handleDrag(e) {
        const axis = this.axis
        const grid = this.grid
        const bounds = this.bounds
        const mouseClickPosition = this.mouseClickPosition

        const tmpDeltaX = axis && axis !== 'y' ? mouseClickPosition.mouseX - (e.touches ? e.touches[0].pageX : e.pageX) : 0
        const tmpDeltaY = axis && axis !== 'x' ? mouseClickPosition.mouseY - (e.touches ? e.touches[0].pageY : e.pageY) : 0

        const [deltaX, deltaY] = snapToGrid(grid, tmpDeltaX, tmpDeltaY, this.scaleRatio)

        const left = restrictToBounds(mouseClickPosition.left - deltaX, bounds.minLeft, bounds.maxLeft)
        const top = restrictToBounds(mouseClickPosition.top - deltaY, bounds.minTop, bounds.maxTop)
        if (this.onDrag(left, top) === false) {
          return
        }
        const right = restrictToBounds(mouseClickPosition.right + deltaX, bounds.minRight, bounds.maxRight)
        const bottom = restrictToBounds(mouseClickPosition.bottom + deltaY, bounds.minBottom, bounds.maxBottom)


        this.left = left
        this.top = top
        this.right = right
        this.bottom = bottom

        // 检测对齐元素
        await this.snapCheck()

        this.$emit('dragging', this.left, this.top)
      },
      moveHorizontally(val) {
        const [deltaX, _] = snapToGrid(this.grid, val, this.top, this.scale)
        const left = restrictToBounds(deltaX, this.bounds.minLeft, this.bounds.maxLeft)
        this.left = left
        this.right = this.parentWidth - this.width - left
      },
      moveVertically(val) {
        const [_, deltaY] = snapToGrid(this.grid, this.left, val, this.scale)
        const top = restrictToBounds(deltaY, this.bounds.minTop, this.bounds.maxTop)
        this.top = top
        this.bottom = this.parentHeight - this.height - top
      },
      // 控制柄移动
      handleResize(e) {
        let left = this.left
        let top = this.top
        let right = this.right
        let bottom = this.bottom

        const mouseClickPosition = this.mouseClickPosition
        const lockAspectRatio = this.lockAspectRatio
        const aspectFactor = this.aspectFactor

        const tmpDeltaX = mouseClickPosition.mouseX - (e.touches ? e.touches[0].pageX : e.pageX)
        const tmpDeltaY = mouseClickPosition.mouseY - (e.touches ? e.touches[0].pageY : e.pageY)

        if (!this.widthTouched && tmpDeltaX) {
          this.widthTouched = true
        }
        if (!this.heightTouched && tmpDeltaY) {
          this.heightTouched = true
        }
        const [deltaX, deltaY] = snapToGrid(this.grid, tmpDeltaX, tmpDeltaY, this.scaleRatio)

        if (this.handle.includes('b')) {
          bottom = restrictToBounds(
            mouseClickPosition.bottom + deltaY,
            this.bounds.minBottom,
            this.bounds.maxBottom
          )
          if (this.lockAspectRatio && this.resizingOnY) {
            right = this.right - (this.bottom - bottom) * aspectFactor
          }
        } else if (this.handle.includes('t')) {
          top = restrictToBounds(
            mouseClickPosition.top - deltaY,
            this.bounds.minTop,
            this.bounds.maxTop
          )
          if (this.lockAspectRatio && this.resizingOnY) {
            left = this.left - (this.top - top) * aspectFactor
          }
        }

        if (this.handle.includes('r')) {
          right = restrictToBounds(
            mouseClickPosition.right + deltaX,
            this.bounds.minRight,
            this.bounds.maxRight
          )
          if (this.lockAspectRatio && this.resizingOnX) {
            bottom = this.bottom - (this.right - right) / aspectFactor
          }
        } else if (this.handle.includes('l')) {
          left = restrictToBounds(
            mouseClickPosition.left - deltaX,
            this.bounds.minLeft,
            this.bounds.maxLeft
          )
          if (this.lockAspectRatio && this.resizingOnX) {
            top = this.top - (this.left - left) / aspectFactor
          }
        }

        const width = computeWidth(this.parentWidth, left, right)
        const height = computeHeight(this.parentHeight, top, bottom)
        if (this.onResize(this.handle, left, top, width, height) === false) {
          return
        }
        this.left = left
        this.top = top
        this.right = right
        this.bottom = bottom
        this.width = width
        this.height = height
        this.$emit('resizing', this.left, this.top, this.width, this.height)
      },

      changeWidth(val) {
        const [newWidth, _] = snapToGrid(this.grid, val, 0, this.scale)
        let right = restrictToBounds(
          (this.parentWidth - newWidth - this.left),
          this.bounds.minRight,
          this.bounds.maxRight
        )
        let bottom = this.bottom
        if (this.lockAspectRatio) {
          bottom = this.bottom - (this.right - right) / this.aspectFactor
        }
        let width = computeWidth(this.parentWidth, this.left, right)
        width = val
        // const height = computeHeight(this.parentHeight, this.top, bottom)
        this.right = right
        this.bottom = bottom
        this.width = width
        // this.height = height
      },
      changeHeight(val) {
        const [_, newHeight] = snapToGrid(this.grid, 0, val, this.scale)
        let bottom = restrictToBounds(
          (this.parentHeight - newHeight - this.top),
          this.bounds.minBottom,
          this.bounds.maxBottom
        )
        let right = this.right
        if (this.lockAspectRatio) {
          right = this.right - (this.bottom - bottom) * this.aspectFactor
        }
        // const width = computeWidth(this.parentWidth, this.left, right)

        let height = computeHeight(this.parentHeight, this.top, bottom)
        height = val

        this.right = right
        this.bottom = bottom
        // this.width = width
        this.height = height
      },
      // 从控制柄松开
      async handleUp(e) {
        this.handle = null

        // 初始化辅助线数据
        const temArr = new Array(3).fill({ display: false, position: '', origin: '', lineLength: '' })
        const refLine = { vLine: [], hLine: [] }
        for (let i in refLine) {
          refLine[i] = JSON.parse(JSON.stringify(temArr))
        }

        if (this.resizing) {
          this.resizing = false
          await this.conflictCheck()
          this.$emit('refLineParams', refLine)
          this.$emit('resizestop', this.left, this.top, this.width, this.height)
        }
        if (this.dragging) {
          this.dragging = false
          await this.conflictCheck()
          this.$emit('refLineParams', refLine)
          this.$emit('dragstop', this.left, this.top)
        }
        this.resetBoundsAndMouseState()
        removeEvent(document.documentElement, eventsFor.move, this.handleResize)
      },

      // 新增方法 ↓↓↓
      // 设置属性
      settingAttribute() {
        // 设置冲突检测
        this.$el.setAttribute('data-is-check', `${this.isConflictCheck}`)
        // 设置对齐元素
        this.$el.setAttribute('data-is-snap', `${this.snap}`)
      },


      //  冲突吸附

      // 冲突检测
      conflictCheck() {
        const top = this.top
        const left = this.left
        const width = this.width
        const height = this.height

        if (this.isConflictCheck) {
          const nodes = this.$el.parentNode.childNodes // 获取当前父节点下所有子节点
          for (let item of nodes) {
            if (item.className !== undefined && !item.className.includes(this.classNameActive) && item.getAttribute('data-is-check') !== null && item.getAttribute('data-is-check') !== 'false') {
              const tw = item.offsetWidth
              const th = item.offsetHeight

              // 正则获取left与top
              let [tl, tt] = this.formatTransformVal(item.style.transform)

              //  1当前 2 比较的
              // 左上角与右下角重叠
              const tfAndBr = (top >= tt && left >= tl && tt + th > top && tl + tw > left) || (top <= tt && left < tl && top + height > tt && left + width > tl)
              // 右上角与左下角重叠
              const brAndTf = (left <= tl && top >= tt && left + width > tl && top < tt + th) || (top < tt && left > tl && top + height > tt && left < tl + tw)
              // 下边与上边重叠
              const bAndT = (top <= tt && left >= tl && top + height > tt && left < tl + tw) || (top >= tt && left <= tl && top < tt + th && left > tl + tw)
              // 上边与下边重叠（宽度不一样）
              const tAndB = (top <= tt && left >= tl && top + height > tt && left < tl + tw) || (top >= tt && left <= tl && top < tt + th && left > tl + tw)
              // 左边与右边重叠
              const lAndR = (left >= tl && top >= tt && left < tl + tw && top < tt + th) || (top > tt && left <= tl && left + width > tl && top < tt + th)
              // 左边与右边重叠（高度不一样）
              const rAndL = (top <= tt && left >= tl && top + height > tt && left < tl + tw) || (top >= tt && left <= tl && top < tt + th && left + width > tl)

              // 如果冲突，就将回退到移动前的位置
              if (tfAndBr || brAndTf || bAndT || tAndB || lAndR || rAndL) {
                this.top = this.mouseClickPosition.top
                this.left = this.mouseClickPosition.left
                this.right = this.mouseClickPosition.right
                this.bottom = this.mouseClickPosition.bottom

                this.width = this.mouseClickPosition.w
                this.height = this.mouseClickPosition.h
              }

            }
          }
        }
      },
      // 检测对齐元素
      async snapCheck() {
        let width = this.width
        let height = this.height
        if (this.snap) {
          let activeLeft = this.left
          let activeRight = this.left + width
          let activeTop = this.top
          let activeBottom = this.top + height

          // 初始化辅助线数据
          const temArr = new Array(3).fill({ display: false, position: '', origin: '', lineLength: '' })
          const refLine = { vLine: [], hLine: [] }
          for (let i in refLine) {
            refLine[i] = JSON.parse(JSON.stringify(temArr))
          }

          const nodes = this.$el.parentNode.childNodes

          let tem = {
            value: { x: [[], [], []], y: [[], [], []] },
            display: [],
            position: []
          }

          const { groupWidth, groupHeight, groupLeft, groupTop, bln } = await this.getActiveAll(nodes)

          if (!bln) {
            width = groupWidth
            height = groupHeight
            activeLeft = groupLeft
            activeRight = groupLeft + groupWidth
            activeTop = groupTop
            activeBottom = groupTop + groupHeight
          }

          for (let item of nodes) {
            if (item.className !== undefined &&
              !item.className.includes(this.classNameActive) &&
              item.getAttribute('data-is-snap') !== null &&
              item.getAttribute('data-is-snap') !== 'false') {
              const w = item.offsetWidth
              const h = item.offsetHeight

              // const w = this.w
              // const h = this.h

              const [l, t] = this.formatTransformVal(item.style.transform)
              const r = l + w // 对齐目标right
              const b = t + h // 对齐目标的bottom

              // console.log(t , h);

              const hc = Math.abs((activeTop + height / 2) - (t + h / 2)) <= this.snapTolerance // 水平中线
              const vc = Math.abs((activeLeft + width / 2) - (l + w / 2)) <= this.snapTolerance // 垂直中线

              const ts = Math.abs(t - activeBottom) <= this.snapTolerance // 从上到下
              const TS = Math.abs(b - activeBottom) <= this.snapTolerance // 从上到下
              const bs = Math.abs(t - activeTop) <= this.snapTolerance // 从下到上
              const BS = Math.abs(b - activeTop) <= this.snapTolerance // 从下到上

              const ls = Math.abs(l - activeRight) <= this.snapTolerance // 外左
              const LS = Math.abs(r - activeRight) <= this.snapTolerance // 外左
              const rs = Math.abs(l - activeLeft) <= this.snapTolerance // 外右
              const RS = Math.abs(r - activeLeft) <= this.snapTolerance // 外右

              tem['display'] = [ts, TS, bs, BS, hc, hc, ls, LS, rs, RS, vc, vc]
              tem['position'] = [t, b, t, b, t + h / 2, t + h / 2, l, r, l, r, l + w / 2, l + w / 2]

              // if (this.left < r && l <= this.left
              //   || this.right < r && l <= this.right) {
              //   // const btc = (b + t) / 2
              //   // if (btc < this.top && btc < b) {
              //   //   this.top = b
              //   //   this.bottom = b + height
              //   //   console.log('!!!!!!!!!!!!!!')
              //   //
              //   //   break
              //   // } else if (t < this.bottom && this.bottom <= btc) {
              //   //   console.log('222222222222')
              //   //
              //   //   this.top = t - this.height
              //   //   this.bottom = b + height
              //   // }
              //
              //   if (this.top < b){
              //     this.top = b
              //     this.bottom = b + height
              //   }
              // }

              if (ts) {
                if (bln) {
                  this.top = t - height
                  this.bottom = this.parentHeight - this.top - height
                }
                tem.value.y[0].push(l, r, activeLeft, activeRight)
              }
              if (bs) {
                if (bln) {
                  this.top = t
                  this.bottom = this.parentHeight - this.top - height
                }
                tem.value.y[0].push(l, r, activeLeft, activeRight)
              }
              if (TS) {
                if (bln) {
                  this.top = b - height
                  this.bottom = this.parentHeight - this.top - height
                }
                tem.value.y[1].push(l, r, activeLeft, activeRight)
              }
              if (BS) {
                if (bln) {
                  this.top = b
                  this.bottom = this.parentHeight - this.top - height
                }
                tem.value.y[1].push(l, r, activeLeft, activeRight)
              }

              if (ls) {
                if (bln) {
                  this.left = l - width
                  this.right = this.parentWidth - this.left - width
                }
                tem.value.x[0].push(t, b, activeTop, activeBottom)
              }
              if (rs) {
                if (bln) {
                  this.left = l
                  this.right = this.parentWidth - this.left - width
                }
                tem.value.x[0].push(t, b, activeTop, activeBottom)
              }
              if (LS) {
                if (bln) {
                  this.left = r - width
                  this.right = this.parentWidth - this.left - width
                }
                tem.value.x[1].push(t, b, activeTop, activeBottom)
              }
              if (RS) {
                if (bln) {
                  this.left = r
                  this.right = this.parentWidth - this.left - width
                }
                tem.value.x[1].push(t, b, activeTop, activeBottom)
              }

              if (hc) {
                if (bln) {
                  this.top = t + h / 2 - height / 2
                  this.bottom = this.parentHeight - this.top - height
                }
                tem.value.y[2].push(l, r, activeLeft, activeRight)
              }
              if (vc) {
                if (bln) {
                  this.left = l + w / 2 - width / 2
                  this.right = this.parentWidth - this.left - width
                }
                tem.value.x[2].push(t, b, activeTop, activeBottom)
              }
              // 辅助线坐标与是否显示(display)对应的数组,易于循环遍历
              const arrTem = [0, 1, 0, 1, 2, 2, 0, 1, 0, 1, 2, 2]
              for (let i = 0; i <= arrTem.length; i++) {
                // 前6为Y辅助线,后6为X辅助线
                const xory = i < 6 ? 'y' : 'x'
                const horv = i < 6 ? 'hLine' : 'vLine'
                if (tem.display[i]) {
                  const { origin, length } = this.calcLineValues(tem.value[xory][arrTem[i]])
                  refLine[horv][arrTem[i]].display = tem.display[i]
                  refLine[horv][arrTem[i]].position = tem.position[i] + 'px'
                  refLine[horv][arrTem[i]].origin = origin
                  refLine[horv][arrTem[i]].lineLength = length
                }
              }
            }
          }
          this.$emit('refLineParams', refLine)
        }
      },

      calcLineValues(arr) {
        const length = Math.max(...arr) - Math.min(...arr) + 'px'
        const origin = Math.min(...arr) + 'px'
        return { length, origin }
      },

      async getActiveAll(nodes) {
        const activeAll = []
        const XArray = []
        const YArray = []
        let groupWidth = 0
        let groupHeight = 0
        let groupLeft = 0
        let groupTop = 0
        for (let item of nodes) {
          if (item.className !== undefined && item.className.includes(this.classNameActive)) {
            activeAll.push(item)
          }
        }
        const AllLength = activeAll.length
        if (AllLength > 1) {
          for (let i of activeAll) {
            const l = i.offsetLeft
            const r = l + i.offsetWidth
            const t = i.offsetTop
            const b = t + i.offsetHeight
            XArray.push(t, b)
            YArray.push(l, r)
          }
          groupWidth = Math.max(...YArray) - Math.min(...YArray)
          groupHeight = Math.max(...XArray) - Math.min(...XArray)
          groupLeft = Math.min(...YArray)
          groupTop = Math.min(...XArray)
        }
        const bln = AllLength === 1
        return { groupWidth, groupHeight, groupLeft, groupTop, bln }
      },
      // 正则获取left与top
      formatTransformVal(string) {
        let [left, top] = string.replace(/[^0-9\-,]/g, '').split(',')
        if (top === undefined) top = 0
        return [+left, +top]
      }
    },
    computed: {
      //  控制手柄的位置
      handleStyle() {
        return (stick) => {
          if (!this.handleInfo.switch) return { display: this.enabled ? 'block' : 'none' }

          const size = (this.handleInfo.size / this.scaleRatio).toFixed(2)
          const offset = (this.handleInfo.offset / this.scaleRatio).toFixed(2)
          const center = (size / 2).toFixed(2)

          const styleMap = {
            tl: {
              top: `${offset}px`,
              left: `${offset}px`
            },
            tm: {
              top: `${offset}px`,
              left: `calc(50% - ${center}px)`
            },
            tr: {
              top: `${offset}px`,
              right: `${offset}px`
            },
            mr: {
              top: `calc(50% - ${center}px)`,
              right: `${offset}px`
            },
            br: {
              bottom: `${offset}px`,
              right: `${offset}px`
            },
            bm: {
              bottom: `${offset}px`,
              right: `calc(50% - ${center}px)`
            },
            bl: {
              bottom: `${offset}px`,
              left: `${offset}px`
            },
            ml: {
              top: `calc(50% - ${center}px)`,
              left: `${offset}px`
            }
          }
          const stickStyle = {
            width: `${size}px`,
            height: `${size}px`,
            top: styleMap[stick].top,
            left: styleMap[stick].left,
            right: styleMap[stick].right,
            bottom: styleMap[stick].bottom
          }
          stickStyle.display = this.enabled ? 'block' : 'none'
          return stickStyle
        }
      },

      //  外面元素的位置
      style() {
        return {
          transform: `translate(${this.left}px, ${this.top}px)`,
          width: this.computedWidth,
          height: this.computedHeight,

          zIndex: this.zIndex,
          ...(this.dragging && this.disableUserSelect ? userSelectNone : userSelectAuto)
        }
      },
      // 控制柄显示与否
      actualHandles() {
        if (!this.resizable) return []

        return this.handles
      },
      computedWidth() {
        if (this.w === 'auto') {
          if (!this.widthTouched) {
            return 'auto'
          }
        }
        return this.width + 'px'
      },
      computedHeight() {
        if (this.h === 'auto') {
          if (!this.heightTouched) {
            return 'auto'
          }
        }
        return this.height + 'px'
      },
      resizingOnX() {
        return (Boolean(this.handle) && (this.handle.includes('l') || this.handle.includes('r')))
      },
      resizingOnY() {
        return (Boolean(this.handle) && (this.handle.includes('t') || this.handle.includes('b')))
      },
      isCornerHandle() {
        return (Boolean(this.handle) && ['tl', 'tr', 'br', 'bl'].includes(this.handle))
      }
    },

    watch: {
      active(val) {
        this.enabled = val

        if (val) {
          this.$emit('activated')
        } else {
          this.$emit('deactivated')
        }
      },
      z(val) {
        if (val >= 0 || val === 'auto') {
          this.zIndex = val
        }
      },
      x(val) {
        if (this.resizing || this.dragging) {
          return
        }

        if (this.parent) {
          this.bounds = this.calcDragLimits()
        }

        this.moveHorizontally(val)
      },
      y(val) {
        if (this.resizing || this.dragging) {
          return
        }

        if (this.parent) {
          this.bounds = this.calcDragLimits()
        }

        this.moveVertically(val)
      },
      lockAspectRatio(val) {
        if (val) {
          this.aspectFactor = this.width / this.height
        } else {
          this.aspectFactor = undefined
        }
      },
      minWidth(val) {
        if (val > 0 && val <= this.width) {
          this.minW = val
        }
      },
      minHeight(val) {
        if (val > 0 && val <= this.height) {
          this.minH = val
        }
      },
      maxWidth(val) {
        this.maxW = val
      },
      maxHeight(val) {
        this.maxH = val
      },
      w(val) {
        if (this.resizing || this.dragging) {
          return
        }

        if (this.parent) {
          this.bounds = this.calcResizeLimits()
        }

        this.changeWidth(val)
      },
      h(val) {
        if (this.resizing || this.dragging) {
          return
        }

        if (this.parent) {
          this.bounds = this.calcResizeLimits()
        }

        this.changeHeight(val)
      }
    }
  }
</script>
